package com.mspbots.script.service.impl;

import cn.hutool.http.HttpException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.mspbots.api.client.CoreClient;
import com.mspbots.script.event.ScriptAction;
import com.mspbots.script.model.SystemScript;
import com.mspbots.script.model.TenantScript;
import com.mspbots.script.model.TriggerEventDTO;
import com.mspbots.script.service.ScriptService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpRequest;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import reactor.core.publisher.Mono;

import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import java.io.IOException;
import java.util.*;

@Slf4j
@Service
public class ScriptServiceImpl implements ScriptService {

    public static final String REDIS_KEY_PREFIX_TENANT = "MSPBOTS:CONFIG:TENANT:";
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private CoreClient coreClient;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private ScriptAction scriptAction;
    @Autowired
    private ScriptEngine scriptEngine;

    @Override
    public Object execute(TenantScript script) throws ScriptException {
        return executeScript(script, null);
    }

    @Override
    public Mono<Object> executeById(TriggerEventDTO triggerEventDTO) {
        return this.coreClient.get("/script/" + triggerEventDTO.getId() + "/info").
                map(script -> this.objectMapper.convertValue(script, new TypeReference<TenantScript>() {
                })).map(s -> {
                return executeGroovyAndResult(s, triggerEventDTO);
        });
    }

    @Override
    public Object executeSystemScript(Long id) {
        JsonNode jsonNode = this.coreClient.get("/system-script/" + id + "/info").block();
        SystemScript systemScript = objectMapper.convertValue(jsonNode, SystemScript.class);
        if (systemScript != null) {
            TenantScript tenantScript = new TenantScript();
            BeanUtils.copyProperties(systemScript, tenantScript);
            tenantScript.setSystemScriptId(systemScript.getId());
            log.debug("exe system trigger");
                return executeGroovyAndResult(tenantScript, null);

        }
        return false;
    }

    @Override
    public Object executeTenantScript(Long id) {
        JsonNode jsonNode = this.coreClient.get("/script/" + id + "/info").block();
        TenantScript tenantScript = objectMapper.convertValue(jsonNode, TenantScript.class);
        if (tenantScript != null) {
                return executeGroovyAndResult(tenantScript, null);
        }
        return false;
    }


    @Override
    public Object executeOne(TriggerEventDTO triggerEventDTO) {
        Map<String, Object> params = Map.of(
                "id", triggerEventDTO.getId(),
//                "tenantId",triggerEventDTO.getTenantId(),
//                "triggerEvent", triggerEventDTO.getEventName(),
//                "scope", triggerEventDTO.getScope(),
                "enable", true);

        JsonNode jsonNode = this.coreClient.get("/script/list", params).block();
        List<TenantScript> list = objectMapper.convertValue(jsonNode, new TypeReference<List<TenantScript>>() {
        });
        if (!CollectionUtils.isEmpty(list)) {
                return executeGroovyAndResult(list.get(0), triggerEventDTO);
        }
        return null;
    }

    @Override
    public Object execute(TriggerEventDTO triggerEventDTO) {
        Map<String, Object> params = Map.of(
                "tenantId", triggerEventDTO.getTenantId(),
                "triggerEvent", triggerEventDTO.getEventName(),
                "scope", triggerEventDTO.getScope(),
                "enable", true);

//        return this.coreClient.get("/script/list", params)
//                .map(jsonNode -> objectMapper.convertValue(jsonNode, new TypeReference<List<TenantScript>>() {}))
//                .switchIfEmpty(Mono.empty())
//                .subscribe(list -> execute(list, triggerEventDTO));
        JsonNode jsonNode = this.coreClient.get("/script/list", params).block();
        List<TenantScript> list = objectMapper.convertValue(jsonNode, new TypeReference<List<TenantScript>>() {
        });
        return execute(list, triggerEventDTO);
    }

    @Override
    public Object execute(List<TenantScript> scripts, TriggerEventDTO triggerEventDTO){
        List<Object> result = new ArrayList<>();
        scripts.stream().forEach(tenantScript -> {
            TriggerEventDTO triggerEvent = new TriggerEventDTO();
            BeanUtils.copyProperties(triggerEventDTO, triggerEvent);
            log.debug("executeScript {} {} {}", tenantScript.getId(), tenantScript.getScope(), tenantScript.getTriggerEvent());
            triggerEvent.setId(tenantScript.getSystemScriptId());
                Object executeResult = executeScript(tenantScript, triggerEvent);
                if (executeResult != null) {
                    result.add(executeResult);
                }

        });
        return result;
    }

    @Override
    public Object execute(List<TenantScript> scripts) {
        return false;
    }


    private Object executeScript(TenantScript tenantScript) throws ScriptException{
        return executeScript(tenantScript, null);
    }

    private Object executeScript(TenantScript tenantScript, TriggerEventDTO triggerEventDTO){
        Object result = null;
        switch (tenantScript.getLanguage()) {
            case Groovy:
                result = executeGroovyAndResult(tenantScript, triggerEventDTO);
                break;
            case Drools:
                result = executeDrools(tenantScript, triggerEventDTO);
                break;
            case JavaScript:
                result = executeJavaScript(tenantScript, triggerEventDTO);
                break;
            default:
                log.error("the script has no language setting ");
                result = false;
                break;
        }

        return result;
    }

    private boolean executeGroovy(TenantScript tenantScript, TriggerEventDTO triggerEventDTO){
        Boolean result = true;
        try {
            executeGroovyAndResult(tenantScript, triggerEventDTO);
        } catch (Exception e) {
            log.error("script {} {} {} {} {} error : {}", tenantScript.getId(), tenantScript.getSystemScriptId(),
                    tenantScript.getName(), tenantScript.getScope(), tenantScript.getTriggerEvent(), e.getMessage());
            e.printStackTrace();
            result = false;
        }
        return result;
    }

    private Object executeGroovyAndResult(TenantScript tenantScript, TriggerEventDTO triggerEventDTO){
        Map<String, Object> params = new HashMap<>();
        params.put("api", scriptAction);

        try {
            if (tenantScript.getParamsValue() != null && !tenantScript.getParamsValue().isNull()) {
                params.put("params", tenantScript.getParamsValue());
            } else if (tenantScript.getSysParamsValue() != null && !tenantScript.getSysParamsValue().isNull()) {
                params.put("params", tenantScript.getSysParamsValue());
            }

            if (tenantScript.getTenantId() != null) {
                Set<String> keys = stringRedisTemplate.keys(REDIS_KEY_PREFIX_TENANT + tenantScript.getTenantId() + ":*");
                ObjectNode tenant = objectMapper.createObjectNode();
                for (String key : keys) {
                    tenant.put(key.replace(REDIS_KEY_PREFIX_TENANT + tenantScript.getTenantId() + ":", ""), stringRedisTemplate.opsForValue().get(key));
                }
                tenant.put("id", tenantScript.getTenantId());
                params.put("tenant", tenant);
            }
            params.put("script", tenantScript);
            if (triggerEventDTO == null) {
                Bindings bindings = scriptEngine.createBindings();
                bindings.putAll(params);
                log.debug("execute script [{}] : {} {}", tenantScript.getLanguage(), tenantScript.getId(), tenantScript.getName());
                return scriptEngine.eval(tenantScript.getContext(), bindings);

            } else {
                params.put("payload", triggerEventDTO.getPayload());
                params.put("event", triggerEventDTO);
                if (!ObjectUtils.isEmpty(triggerEventDTO.getUserInfo()) && !triggerEventDTO.getUserInfo().isNull()) {
                    params.put("user", triggerEventDTO.getUserInfo());
                }
                Bindings bindings = scriptEngine.createBindings();
                bindings.putAll(params);
                log.debug("execute script [{}] : {}", tenantScript.getLanguage(), tenantScript.getId());
                log.debug("execute params [{}] ", params.size());
                return scriptEngine.eval(tenantScript.getContext(), bindings);
            }
        } catch (Exception e) {
            log.error("script {} {} {} {} {} error : {}", tenantScript.getId(), tenantScript.getSystemScriptId(),
                    tenantScript.getName(), tenantScript.getScope(), tenantScript.getTriggerEvent(), e.getMessage());
            e.printStackTrace();
        }
        return null;

    }

    private boolean executeDrools(TenantScript tenantScript, TriggerEventDTO triggerEventDTO) {
       /* var builder = KnowledgeBuilderFactory.newKnowledgeBuilder();
        //load rules as DRL format
        builder.add(ResourceFactory.newByteArrayResource(tenantScript.getContext().getBytes()), ResourceType.DRL);
        KnowledgeBuilderErrors errors = builder.getErrors();

        //valid rule
        for (KnowledgeBuilderError error : errors) {
            log.error(error.getMessage());
        }
        InternalKnowledgeBase kBase = KnowledgeBaseFactory.newKnowledgeBase();
        kBase.addPackages(builder.getKnowledgePackages());
        KieSession kieSession = kBase.newKieSession();


        kieSession.insert(triggerEventDTO);
        int i = kieSession.fireAllRules();
        */
        return false;
    }

    private boolean executeJavaScript(TenantScript tenantScript, TriggerEventDTO triggerEventDTO) {
        log.error("the javascript engine is Unavailable");
        return false;
    }
}
